package com.google.code.opengw.http;

import java.io.IOException;
import java.net.Socket;



public class Connection {
	private IOSocket connectionSocket;
	private int readTimeout;
	private int connectionBufferSize;
	
	public Connection(IOSocket socket, int readTimeout, int connectionBufferSize){
		this.connectionSocket = socket;
		this.readTimeout = readTimeout;
		this.connectionBufferSize = connectionBufferSize;
	}
	
	public void invoke(Request request, Socket clientSocket)throws HTTPException, IOException {
		connectionSocket.connect();
		connectionSocket.write(request.getHTTPHeaderString(true).getBytes());
		if(request.getContent() != null)
			connectionSocket.write(request.getContent());
		connectionSocket.flush();
		Response response = new Response();
		
		byte[] buffer = new byte[connectionBufferSize];
		int bufferIndex = 0;
		int bufferReadLength = 0;
		StringBuilder line = new StringBuilder("");
		boolean firstLine = false;
		boolean headerComplete = false;

		while(!headerComplete){
			connectionSocket.setSoTimeout(readTimeout);
			bufferReadLength = connectionSocket.read(buffer);
			if(bufferReadLength == -1){
				throw new HTTPException("NOT FOUND", 404);
			}
			for( ; bufferIndex < bufferReadLength ; bufferIndex++ ){
				char ch = (char) buffer[bufferIndex];
				line.append(ch);
				if(ch == '\n'){
					if(line.toString().equals("\r\n")){
						headerComplete = true;
						bufferIndex++;
						break;
					}
					if(!firstLine){
						String[] firstLineArr = line.toString().split(" ");
						response.setProtocol(firstLineArr[0]);
						response.setResponseCode(Integer.parseInt(firstLineArr[1]));
						response.setResponseDetail((firstLineArr[2]).trim());
						firstLine = true;
					} else{
						HTTPHeader header = new HTTPHeader(line.toString());
						response.addHeader(header);
					}
					line = new StringBuilder("");
				}
			}
		}
		clientSocket.getOutputStream().write(response.getHTTPHeaderString(true).getBytes());
		clientSocket.getOutputStream().flush();
		if(response.isChunked()){
			int readContentLength = 0;
			int chunkLength = -1;
			int readChunkLenght = 0;
			int chunkDataPartLength = 0;
			boolean continueProcess = true;
			boolean bufferEmpty = (bufferIndex < bufferReadLength) ? false : true;
			boolean isReadChunkLength = true;
			boolean isReadChunkLegthContinue = false;
			StringBuilder chunkLengthValue = null;
			if(!bufferEmpty){
				clientSocket.getOutputStream().write(buffer, bufferIndex, bufferReadLength - bufferIndex);
				clientSocket.getOutputStream().flush();
			}
			
			while(continueProcess){
				if(bufferEmpty){
					buffer = new byte[connectionBufferSize];
					connectionSocket.setSoTimeout(readTimeout);
					bufferReadLength = connectionSocket.read(buffer);
					bufferIndex = 0;
					clientSocket.getOutputStream().write(buffer, bufferIndex, bufferReadLength);
					clientSocket.getOutputStream().flush();
				}

				if(isReadChunkLength){
					chunkLength = -1;
					if(!isReadChunkLegthContinue)
						chunkLengthValue = new StringBuilder();
					for( ; bufferIndex < bufferReadLength ; bufferIndex++ ){
						char ch = (char) buffer[bufferIndex];
						chunkLengthValue.append(ch);
						if(ch == '\n'){
							if(chunkLengthValue.toString().endsWith("\r\n") && !chunkLengthValue.toString().trim().equals("")){
								bufferIndex++;
								chunkLength = Integer.parseInt(chunkLengthValue.toString().trim(), 16);
								readChunkLenght = 0;
								break;
							}
						}
							
					}
					if(chunkLength == -1){
						continueProcess = true;
						bufferEmpty = true;
						isReadChunkLength = true;
						isReadChunkLegthContinue = true;
						continue;
					}
					
					chunkDataPartLength = (chunkLength > (bufferReadLength - bufferIndex)) ? (bufferReadLength - bufferIndex) : chunkLength;
				} else {
					chunkDataPartLength = (chunkLength > (bufferReadLength + readChunkLenght)) ? bufferReadLength : chunkLength - readChunkLenght;
				}
				
				//END OF CONTENT
				if(chunkLength == 0)
					break;
				
				if(readChunkLenght < chunkLength ){
					readContentLength += chunkDataPartLength;
					readChunkLenght += chunkDataPartLength;
					bufferIndex += chunkDataPartLength;
				}
				if(chunkLength > readChunkLenght){
					continueProcess = true;
					bufferEmpty = true;
					isReadChunkLength = false;
					isReadChunkLegthContinue = false;
				} else if(bufferIndex == bufferReadLength){
					continueProcess = true;
					bufferEmpty = true;
					isReadChunkLength = true;
					isReadChunkLegthContinue = false;
				} else {
					continueProcess = true;
					bufferEmpty = false;
					isReadChunkLength = true;
					isReadChunkLegthContinue = false;
				}
				
			}
		} else if(response.getContentLength() > 0){
			int contentLength = response.getContentLength();
			int readContentLength = 0;
			if(bufferReadLength != bufferIndex){
				clientSocket.getOutputStream().write(buffer, bufferIndex, bufferReadLength - bufferIndex);
				clientSocket.getOutputStream().flush();
				readContentLength += (bufferReadLength - bufferIndex);
			}
            while(readContentLength < contentLength){
            	connectionSocket.setSoTimeout(readTimeout);
            	int len = connectionSocket.read(buffer);
            	clientSocket.getOutputStream().write(buffer, 0, len);
            	clientSocket.getOutputStream().flush();
	            readContentLength += len;
	        }
		}
	}
	
	public Response invoke(Request request) throws HTTPException, IOException {
		connectionSocket.connect();
		connectionSocket.write(request.getHTTPHeaderString(true).getBytes());
		if(request.getContent() != null)
			connectionSocket.write(request.getContent());
		connectionSocket.flush();
		
		Response response = new Response();
		
		byte[] buffer = new byte[connectionBufferSize];
		int bufferIndex = 0;
		int bufferReadLength = 0;
		StringBuilder line = new StringBuilder("");
		boolean firstLine = false;
		boolean headerComplete = false;

		while(!headerComplete){
			connectionSocket.setSoTimeout(readTimeout);
			bufferReadLength = connectionSocket.read(buffer);
			if(bufferReadLength == -1){
				return new Response(404);
			}
			for( ; bufferIndex < bufferReadLength ; bufferIndex++ ){
				char ch = (char) buffer[bufferIndex];
				line.append(ch);
				if(ch == '\n'){
					if(line.toString().equals("\r\n")){
						headerComplete = true;
						bufferIndex++;
						break;
					}
					if(!firstLine){
						String[] firstLineArr = line.toString().split(" ");
						response.setProtocol(firstLineArr[0]);
						response.setResponseCode(Integer.parseInt(firstLineArr[1]));
						response.setResponseDetail((firstLineArr[2]).trim());
						firstLine = true;
					} else{
						HTTPHeader header = new HTTPHeader(line.toString());
						response.addHeader(header);
					}
					line = new StringBuilder("");
				}
			}
		}
		if(response.isChunked()){
			
			byte[] content = null;
			int readContentLength = 0;
			int chunkLength = -1;
			int readChunkLenght = 0;
			int chunkDataPartLength = 0;
			boolean continueProcess = true;
			boolean bufferEmpty = (bufferIndex < bufferReadLength) ? false : true;
			boolean isReadChunkLength = true;
			boolean isReadChunkLegthContinue = false;
			StringBuilder chunkLengthValue = null;
			while(continueProcess){
				if(bufferEmpty){
					buffer = new byte[connectionBufferSize];
					connectionSocket.setSoTimeout(readTimeout);
					bufferReadLength = connectionSocket.read(buffer);
					bufferIndex = 0;
				}
				if(isReadChunkLength){
					chunkLength = -1;
					if(!isReadChunkLegthContinue)
						chunkLengthValue = new StringBuilder();
					for( ; bufferIndex < bufferReadLength ; bufferIndex++ ){
						char ch = (char) buffer[bufferIndex];
						chunkLengthValue.append(ch);
						if(ch == '\n'){
							if(chunkLengthValue.toString().endsWith("\r\n") && !chunkLengthValue.toString().trim().equals("")){
								bufferIndex++;
								chunkLength = Integer.parseInt(chunkLengthValue.toString().trim(), 16);
								readChunkLenght = 0;
								break;
							}
						}
							
					}
					if(chunkLength == -1){
						continueProcess = true;
						bufferEmpty = true;
						isReadChunkLength = true;
						isReadChunkLegthContinue = true;
						continue;
					}
					
					chunkDataPartLength = (chunkLength > (bufferReadLength - bufferIndex)) ? (bufferReadLength - bufferIndex) : chunkLength;
				} else {
					chunkDataPartLength = (chunkLength > (bufferReadLength + readChunkLenght)) ? bufferReadLength : chunkLength - readChunkLenght;
				}
				
				//END OF CONTENT
				if(chunkLength == 0)
					break;
				
				if(readChunkLenght < chunkLength ){
					readContentLength += chunkDataPartLength;
					readChunkLenght += chunkDataPartLength;
					if(content == null){
						content = new byte[readContentLength];
						System.arraycopy(buffer, bufferIndex, content, 0, chunkDataPartLength);
					} else {
						byte[] tmpContent = content;
						content = new byte[readContentLength];
						System.arraycopy(tmpContent, 0, content, 0, tmpContent.length);
						System.arraycopy(buffer, bufferIndex, content, tmpContent.length, chunkDataPartLength);
					}
					bufferIndex += chunkDataPartLength;
					
				}
				if(chunkLength > readChunkLenght){
					continueProcess = true;
					bufferEmpty = true;
					isReadChunkLength = false;
					isReadChunkLegthContinue = false;
				} else if(bufferIndex == bufferReadLength){
					continueProcess = true;
					bufferEmpty = true;
					isReadChunkLength = true;
					isReadChunkLegthContinue = false;
				} else {
					continueProcess = true;
					bufferEmpty = false;
					isReadChunkLength = true;
					isReadChunkLegthContinue = false;
				}
				
			}
			
			response.setContent(content);
				
		} else if(response.getContentLength() > 0){
			int contentLength = response.getContentLength();
			int readContentLength = 0;
			byte[] content = new byte[contentLength];
			if(bufferReadLength != bufferIndex){
				System.arraycopy(buffer, bufferIndex, content, 0, bufferReadLength - bufferIndex);
				readContentLength += (bufferReadLength - bufferIndex);
			}
            while(readContentLength < contentLength){
            	connectionSocket.setSoTimeout(readTimeout);
	            int len = connectionSocket.read(content, readContentLength, contentLength - readContentLength);
	            readContentLength += len;
	        }
            
            response.setContent(content);
		}
		
		return response;
	}
}
